package com.nkym.wypan.service.impl;

import java.util.List;

import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.nkym.wypan.constants.FileConstants;
import com.nkym.wypan.enums.FileDelFlag;
import com.nkym.wypan.enums.FolderFlag;
import com.nkym.wypan.enums.ResponseStatus;
import com.nkym.wypan.event.SearchEvent;
import com.nkym.wypan.exception.BusinessException;
import com.nkym.wypan.mapper.UserFileMapper;
import com.nkym.wypan.model.context.recycle.RecyclePageContext;
import com.nkym.wypan.model.context.userfile.*;
import com.nkym.wypan.model.entity.UserFile;
import com.nkym.wypan.model.resp.userfile.BreadcrumbVo;
import com.nkym.wypan.model.resp.userfile.PageQueryUserFileResp;
import com.nkym.wypan.model.resp.userfile.QueryUserFileVo;
import com.nkym.wypan.model.resp.userfile.SearchVo;
import com.nkym.wypan.service.UserFileService;
import com.nkym.wypan.utils.IdUtils;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.util.Lists;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 19637
 * @description 针对表【wy_pan_user_file】的数据库操作Service实现
 * @createDate 2023-06-07 18:38:09
 */
@Service
@Slf4j
public class UserFileServiceImpl extends ServiceImpl<UserFileMapper, UserFile>
        implements UserFileService, ApplicationContextAware {
    @Resource
    UserFileMapper userFileMapper;
    ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public List<UserFile> findAllFile(List<Long> shareFileIdList) {
        List<UserFile> userFileList = userFileMapper.selectBatchIds(shareFileIdList);
        return findAllFileRecords(userFileList);
    }

    private List<UserFile> findAllFileRecords(List<UserFile> userFileList) {
        ArrayList<UserFile> allRecords = Lists.newArrayList(userFileList);
        if (allRecords.isEmpty()) {
            return allRecords;
        }
        long count = allRecords.stream().filter(r -> Objects.equals(r.getFolderFlag(), FolderFlag.YES.getCode())).count();
        if (count == 0) return allRecords;
        userFileList.forEach(r -> {
            doFindAllFileRecords(allRecords, r);
        });
        return allRecords;
    }

    private void doFindAllFileRecords(ArrayList<UserFile> allRecords, UserFile r) {
        if (r == null) {
            return;
        }
        if (Objects.equals(r.getFolderFlag(), FolderFlag.NO.getCode())) {
            return;
        }
        List<UserFile> childRecords = findAllChildFile(r);
        allRecords.addAll(childRecords);
        childRecords.forEach(child -> {
            doFindAllFileRecords(allRecords, child);
        });
    }

    private List<UserFile> findAllChildFile(UserFile r) {
        List<UserFile> res = userFileMapper.queryFileByParentId(r.getFileId());
        return res;
    }

    @Override
    public List<BreadcrumbVo> breadcrumb(BreadcrumbContext breadcrumbContext) {
        Long fileId = breadcrumbContext.getFileId();
        Long userId = breadcrumbContext.getUserId();
        List<UserFile> allFolder = getAllFolder(userId);
        Map<Long, BreadcrumbVo> prepareBreadcrumbVoMap = allFolder.stream().map(BreadcrumbVo::transfer).collect(Collectors.toMap(BreadcrumbVo::getId, a -> a));
        BreadcrumbVo currentNode;
        List<BreadcrumbVo> res = new LinkedList<>();
        do {
            currentNode = prepareBreadcrumbVoMap.get(fileId);
            if (currentNode != null) {
                res.add(0, currentNode);
                fileId = currentNode.getParentId();
            }
        } while (ObjectUtil.isNotNull(currentNode));
        return res;
    }

    private List<UserFile> getAllFolder(Long uid) {
        List<UserFile> result = userFileMapper.selectList(new LambdaQueryWrapper<UserFile>().eq(UserFile::getFolderFlag, FolderFlag.YES.getCode())
                .eq(UserFile::getUserId, uid));
        return result;


    }

    @Override
    public List<UserFile> recyclePageList(RecyclePageContext context) {
        List<UserFile> u = userFileMapper.recyclePageList(context);
        return u;
    }

    /**
     * 1、校验权限 能否删除
     * 2、删除文件
     *
     * @param fileDeleteContext
     */
    @Override
    public void recycleFile(FileDeleteContext fileDeleteContext) {
        List<UserFile> userFileList = checkDeletePermission(fileDeleteContext);
        doRecycleFile(fileDeleteContext, userFileList);
        postProcessor(fileDeleteContext);
    }

    /**
     * 将文件放入回收站
     * 只有 UserFileList中的会被放入回收站，子目录会被修改为删除状态
     *
     * @param fileDeleteContext 文件删除上下文
     * @param userFileList      放入回收站的用户集合
     */
    private void doRecycleFile(FileDeleteContext fileDeleteContext, List<UserFile> userFileList) {
        Long userId = fileDeleteContext.getUserId();
        //需要修改为删除状态的用户文件集合
        List<UserFile> allDeleteFile = Lists.newArrayList();
        //找到用户所有未删除的文件,并且映射成map
        //Map<目录ID，当前目录所有文件>
        Map<Long, List<UserFile>> map = userFileMapper.selectList(new LambdaQueryWrapper<UserFile>()
                        .eq(UserFile::getUserId, userId)
                        .eq(UserFile::getDelFlag, FileDelFlag.NORMAL.getCode()))
                .stream()
                .collect(Collectors.groupingBy(UserFile::getParentId));
        for (UserFile userFile : userFileList) {
            if (FolderFlag.YES.getCode().equals(userFile.getFolderFlag())) {
                findAllDeleteFile(allDeleteFile, userFile, map);
            }
        }
        //放入回收站
        userFileList = userFileList.stream().map(r -> {
            r.setDelFlag(FileDelFlag.RECYCLE.getCode());
            r.setUpdateTime(new Date());
            return r;
        }).collect(Collectors.toList());
        allDeleteFile = allDeleteFile.stream().map(r -> {
            r.setDelFlag(FileDelFlag.RECYCLE_HIDDEN.getCode());
            r.setUpdateTime(new Date());
            return r;
        }).collect(Collectors.toList());
        //批量修改
        List<UserFile> r = new ArrayList<>();
        r.addAll(userFileList);
        r.addAll(allDeleteFile);
        boolean b = this.updateBatchById(r);
        if (!b) {
            throw new BusinessException("文件删除失败");
        }
    }

    private void findAllDeleteFile(List<UserFile> allDeleteFile, UserFile userFile, Map<Long, List<UserFile>> map) {
        //不是目录文件 ，阻断递归
        if (FolderFlag.NO.getCode().equals(userFile.getFolderFlag())) {
            return;
        }
        Long fid = userFile.getFileId();
        List<UserFile> userFileList = map.getOrDefault(fid, new ArrayList<>());
        for (UserFile u : userFileList) {
            allDeleteFile.add(u);
            if (FolderFlag.YES.getCode().equals(u.getFolderFlag())) {
                findAllDeleteFile(allDeleteFile, u, map);
            }
        }
    }

    /**
     * 用于事件发布，更新分享状态等等。。。。
     *
     * @param fileDeleteContext
     */
    private void postProcessor(FileDeleteContext fileDeleteContext) {

    }

    private List<UserFile> checkDeletePermission(FileDeleteContext fileDeleteContext) {
        Long userId = fileDeleteContext.getUserId();
        List<Long> fileIds = fileDeleteContext.getFileIds();
        List<UserFile> userFileList = userFileMapper.selectList(new LambdaQueryWrapper<UserFile>()
                .in(UserFile::getFileId, fileIds));
        if (userFileList == null || userFileList.size() == 0) {
            throw new BusinessException("删除的文件存在非法记录");
        }
        if (fileIds.size() != userFileList.size()) {
            throw new BusinessException("删除的文件存在非法记录");
        }
        Set<Long> userIdSet = userFileList
                .stream()
                .map(UserFile::getUserId)
                .collect(Collectors.toSet());
        if (userIdSet.size() != 1) {
            throw new BusinessException("非法操作,无权删除");
        }
        boolean b = userIdSet.stream().allMatch(userId::equals);
        if (!b) {
            throw new BusinessException("非法操作,无权删除");
        }
        return userFileList;
    }


    @Override
    public List<SearchVo> searchFile(SearchContext searchContext) {

        List<UserFile> searchList = userFileMapper.search(searchContext);
        List<SearchVo> searchVoList = searchList.stream().map(SearchVo::cover2SearchVo).collect(Collectors.toList());
        //发布搜索历史保存事件
        publishSearchEvent(searchContext);
        return searchVoList;
    }

    private void publishSearchEvent(SearchContext searchContext) {
        String keyword = searchContext.getKeyword();
        Long userId = searchContext.getUserId();
        SearchEvent searchEvent = new SearchEvent();
        searchEvent.setUserId(userId);
        searchEvent.setSearchContent(keyword);
        applicationContext.publishEvent(searchEvent);
    }

    @Override
    @Transactional
    public void transferFile(FileTransferContext context) {
        List<UserFile> userFileList = checkContext(context);
        doTransfer(userFileList, context);
    }


    @Override
    public void saveUserFile(UserFile userFile) {
        Long userId = userFile.getUserId();
        Long parentId = userFile.getParentId();
        String filename = userFile.getFilename();
        Integer folderFlag = userFile.getFolderFlag();
        boolean b = checkDuplicationName(userId, parentId, filename, folderFlag);
        if (b) {
            int lastIndex = filename.lastIndexOf(FileConstants.POINT);
            filename = new StringBuilder().append(filename.substring(0, lastIndex))
                    .append("_").
                    append(System.currentTimeMillis())
                    .append(filename.substring(lastIndex)).toString();
            userFile.setFilename(filename);
        }
        //插入信息
        userFileMapper.insert(userFile);
    }

    @Override
    public void renameFile(FileRenameContext context) {
        Long userId = context.getUserId();
        Long fileId = context.getFileId();
        String newFilename = context.getNewFilename();


        UserFile userFile = userFileMapper.selectById(fileId);
        //校验文件是否存在
        if (userFile == null) {
            throw new BusinessException(ResponseStatus.FILE_NOT_EXIST);
        }
        Long readUserId = userFile.getUserId();
        Long parentId = userFile.getParentId();
        Integer folderFlag = userFile.getFolderFlag();
        //是否当前用户拥有
        if (!readUserId.equals(userId)) {
            throw new BusinessException(ResponseStatus.ILLEGAL_OPERATION);
        }
        //禁止重复命名
        //todo  根据正则重复命名时在后面添加数字
        boolean b = checkDuplicationName(userId, parentId, newFilename, folderFlag);
        if (b) {
            throw new BusinessException(ResponseStatus.FILENAME_EXIST);
        }
        //修改文件名
        userFileMapper.renameFile(fileId, newFilename);
    }

    @Override
    public void addFolder(AddUserFolderContext context) {
        createFolder(context);
    }

    @Override
    public PageQueryUserFileResp list(QueryUserFileContext context) {
        Long userId = context.getUserId();
        Long parentId = context.getParentId();
        Integer delFlag = context.getDelFlag();
        Integer offset = context.getOffset();
        Integer size = context.getSize();
        if (parentId != 0) {
            UserFile file = userFileMapper.selectOne(new LambdaQueryWrapper<UserFile>()
                    .eq(UserFile::getUserId, userId)
                    .eq(UserFile::getFileId, parentId)
                    .eq(UserFile::getDelFlag, delFlag));
            if (file == null) {
                throw new BusinessException(ResponseStatus.FOLDER_NOT_EXIST);
            }
            if (FolderFlag.NO.getCode().equals(file.getFolderFlag())) {
                throw new BusinessException(ResponseStatus.FILE_IS_NOT_FOLDER);
            }
        }
        Long totalRecord = userFileMapper.selectCount(new LambdaQueryWrapper<UserFile>()
                .eq(UserFile::getUserId, userId)
                .eq(UserFile::getParentId, parentId)
                .eq(UserFile::getDelFlag, delFlag));

        List<QueryUserFileVo> queryUserFileVoList = userFileMapper.list(context);
        Long totalPage = (totalRecord + size - 1) / size;
        PageQueryUserFileResp resp = new PageQueryUserFileResp();
        resp.setQueryUserFileVos(queryUserFileVoList);
        resp.setTotalPage(totalPage);
        return resp;
    }

    @Override
    public void createFolder(AddUserFolderContext context) {
        Long userId = context.getUserId();
        Long parentId = context.getParentId();
        String filename = context.getFilename();
        Integer folderFlag = context.getFolderFlag();
        //生成全局唯一ID
        long fileId = IdUtils.getId();
        //禁止重复文件名
        boolean b = checkDuplicationName(userId, parentId, filename, folderFlag);
        if (b) {
            int lastIndex = filename.lastIndexOf(FileConstants.POINT);
            filename = new StringBuilder().append(filename, 0, lastIndex)
                    .append("_")
                    .append(System.currentTimeMillis())
                    .append(filename.substring(lastIndex)).toString();
        }
        UserFile userFile = cover2UserFile(fileId, userId, parentId, null, filename, folderFlag, null, null, null, userId, userId);
        //保存文件
        if (!save(userFile)) {
            throw new BusinessException(ResponseStatus.FAIL);
        }
        ;
    }


    /***
     * -------------------------------private-------------------------------------------------
     *
     */
    /**
     * 检查重复命名
     *
     * @param userId
     * @param parentId
     * @param filename
     * @param folderFlag
     * @return
     */
    private boolean checkDuplicationName(Long userId, Long parentId, String filename, Integer folderFlag) {
        List<String> allFileName = userFileMapper.selectDuplicateName(userId, parentId, folderFlag);
        boolean b = allFileName.stream().anyMatch(filename::equals);
        return b;
    }

    /**
     * 转移文件
     *
     * @param userFileList
     * @param context
     */
    private void doTransfer(List<UserFile> userFileList, FileTransferContext context) {
        Long targetFileId = context.getTargetFileId();
        Long userId = context.getUserId();
        List<String> filenames = userFileMapper.selectDuplicateName(userId, targetFileId, FolderFlag.YES.getCode());
        userFileList = userFileList.stream().map(r -> {
            r.setParentId(targetFileId);
            boolean b = filenames.stream().anyMatch(a -> a.equals(r.getFilename()));
            if (b) {
                r.setFilename(UUID.randomUUID().toString());
            }
            return r;
        }).collect(Collectors.toList());
        if (!this.updateBatchById(userFileList)) {
            throw new BusinessException("失败");
        }


    }

    /**
     * 检查目标文件是否 文件夹
     * 转移文件不能是目标文件夹的  文件夹 。
     *
     * @param context
     */
    private List<UserFile> checkContext(FileTransferContext context) {
        Long userId = context.getUserId();
        List<Long> fileIds = context.getFileIds();
        Long targetFileId = context.getTargetFileId();
        UserFile userFile = userFileMapper.selectOne(new LambdaQueryWrapper<UserFile>()
                .eq(UserFile::getFileId, targetFileId)
                .eq(UserFile::getUserId, userId));

        if (userFile == null || FolderFlag.NO.getCode().equals(userFile.getFolderFlag())) {
            throw new BusinessException("目标文件不是文件夹或不存在");
        }
        //需要转移的文件信息
        List<UserFile> transferFile = userFileMapper.selectBatchIds(fileIds);
        //所有文件需要是属于当前用户的
        transferFile = transferFile.stream().filter(r -> r.getUserId().equals(userId)).collect(Collectors.toList());
        //查询用户所有文件
        List<UserFile> userFiles = userFileMapper.selectList(new LambdaQueryWrapper<UserFile>()
                .eq(UserFile::getUserId, userId));

        //用户所有文件夹
        List<UserFile> folderList = userFiles.stream().filter((a) -> FolderFlag.YES.getCode().equals(a.getFolderFlag())).collect(Collectors.toList());
        //对扁平数据做树状处理
        Map<Long, List<UserFile>> map = folderList.stream().collect(Collectors.groupingBy(UserFile::getParentId));
        //判断能否转移
        if (checkIsChildFolder(map, transferFile, targetFileId)) {
            throw new BusinessException(ResponseStatus.PARAM_ERROR);
        }
        return transferFile;
    }

    /**
     * 检查转移文件是否是 目标文件夹的子目录
     *
     * @param map
     * @param transferFile
     * @return
     */
    private boolean checkIsChildFolder(Map<Long, List<UserFile>> map, List<UserFile> transferFile, Long targetId) {
        //转移文件中的所有目录文件
        List<UserFile> transferFolder = transferFile.stream().filter((a) -> FolderFlag.YES.getCode().equals(a.getFolderFlag())).collect(Collectors.toList());
        if (transferFolder.size() == 0) {
            return false;
        }
        //对于当前所有转移文件 不能转移到的目标文件夹
        List<UserFile> unavailableFolderList = Lists.newArrayList();
        unavailableFolderList.addAll(map.get(targetId));
        //找到所有不可转移的文件目录
        transferFile.forEach((r) -> findAllChildFolder(unavailableFolderList, map, r));
        //判断是否包含目标文件夹Id
        return unavailableFolderList.stream().map(UserFile::getFileId).anyMatch(targetId::equals);
    }

    private void findAllChildFolder(List<UserFile> unavailableFolderList, Map<Long, List<UserFile>> map, UserFile r) {
        List<UserFile> childFolder = map.get(r.getFileId());
        if (childFolder == null || childFolder.size() == 0) {
            return;
        }
        unavailableFolderList.addAll(childFolder);
        childFolder.forEach((res) -> findAllChildFolder(unavailableFolderList, map, res));
    }


    private UserFile cover2UserFile(Long fileId, Long userId, Long parentId,
                                    Long realFileId, String filename, Integer folderFlag,
                                    Long fileSize,
                                    Integer fileType, Integer delFlag,
                                    Long createUserId, Long updateUserId) {
        UserFile userFile = new UserFile();
        userFile.setFileId(fileId);
        userFile.setUserId(userId);
        userFile.setParentId(parentId);
        userFile.setRealFileId(realFileId);
        userFile.setFilename(filename);
        userFile.setFolderFlag(folderFlag);
        userFile.setFileSize(fileSize);
        userFile.setFileType(fileType);
        userFile.setDelFlag(delFlag);
        userFile.setCreateUser(createUserId);
        userFile.setUpdateUser(updateUserId);
        return userFile;
    }
}




